package mysqls

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"qingbing/xztools/crons"
	"qingbing/xzutils/files"
	"time"

	"gorm.io/gorm/logger"
	"gorm.io/gorm/utils"
)

// Writer log writer interface
type Writer interface {
	Printf(string, ...interface{})
}

// Colors
const (
	Reset       = "\033[0m"
	Red         = "\033[31m"
	Green       = "\033[32m"
	Yellow      = "\033[33m"
	Blue        = "\033[34m"
	Magenta     = "\033[35m"
	Cyan        = "\033[36m"
	White       = "\033[37m"
	BlueBold    = "\033[34;1m"
	MagentaBold = "\033[35;1m"
	RedBold     = "\033[31;1m"
	YellowBold  = "\033[33;1m"
)

// Config logger config
type ConfLog struct {
	logger.Config
	// 文件日志使用
	TimeInterval crons.TimeInterval
	LogDir       string
	Prefix       string
}

type Logger struct {
	Writer
	ConfLog
	infoStr, warnStr, errStr            string
	traceStr, traceErrStr, traceWarnStr string

	// 私有属性
	fp   *os.File
	task *crons.Cron
}

// LogMode log mode
func (l *Logger) LogMode(level logger.LogLevel) logger.Interface {
	newLogger := *l
	newLogger.LogLevel = level
	return &newLogger
}

// Info print info
func (l *Logger) Info(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= logger.Info {
		l.Printf(l.infoStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

// Warn print warn messages
func (l *Logger) Warn(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= logger.Warn {
		l.Printf(l.warnStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

// Error print error messages
func (l *Logger) Error(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= logger.Error {
		l.Printf(l.errStr+msg, append([]interface{}{utils.FileWithLineNum()}, data...)...)
	}
}

// Trace print sql message
func (l *Logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if l.LogLevel <= logger.Silent {
		return
	}

	elapsed := time.Since(begin)
	switch {
	case err != nil && l.LogLevel >= logger.Error && (!errors.Is(err, ErrRecordNotFound) || !l.IgnoreRecordNotFoundError):
		sql, rows := fc()
		if rows == -1 {
			l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			l.Printf(l.traceErrStr, utils.FileWithLineNum(), err, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= logger.Warn:
		sql, rows := fc()
		slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold)
		if rows == -1 {
			l.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			l.Printf(l.traceWarnStr, utils.FileWithLineNum(), slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	case l.LogLevel == logger.Info:
		sql, rows := fc()
		if rows == -1 {
			l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, "-", sql)
		} else {
			l.Printf(l.traceStr, utils.FileWithLineNum(), float64(elapsed.Nanoseconds())/1e6, rows, sql)
		}
	}
}

// ParamsFilter filter params
func (l *Logger) ParamsFilter(ctx context.Context, sql string, params ...interface{}) (string, []interface{}) {
	if l.ParameterizedQueries {
		return sql, nil
	}
	return sql, params
}

/**
 * 启用日志
 */
func (l *Logger) Run() {
	/**
	 * 颜色赋值
	 */
	if l.Colorful {
		l.infoStr = Green + "%s\n" + Reset + Green + "[info] " + Reset
		l.warnStr = BlueBold + "%s\n" + Reset + Magenta + "[warn] " + Reset
		l.errStr = Magenta + "%s\n" + Reset + Red + "[error] " + Reset
		l.traceStr = Green + "%s\n" + Reset + Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s"
		l.traceWarnStr = Green + "%s " + Yellow + "%s\n" + Reset + RedBold + "[%.3fms] " + Yellow + "[rows:%v]" + Magenta + " %s" + Reset
		l.traceErrStr = RedBold + "%s " + MagentaBold + "%s\n" + Reset + Yellow + "[%.3fms] " + BlueBold + "[rows:%v]" + Reset + " %s"
	} else {
		l.infoStr = "%s\n[info] "
		l.warnStr = "%s\n[warn] "
		l.errStr = "%s\n[error] "
		l.traceStr = "%s\n[%.3fms] [rows:%v] %s"
		l.traceWarnStr = "%s %s\n[%.3fms] [rows:%v] %s"
		l.traceErrStr = "%s %s\n[%.3fms] [rows:%v] %s"
	}
	/**
	 * 确保 Writer 存在
	 */
	if l.Writer == nil {
		/**
		 * 确保目录存在
		 */
		if l.LogDir == "" {
			panic(fmt.Errorf("SQL日志未配置存放目录"))
		}
		if _, err := os.Stat(l.LogDir); os.IsNotExist(err) {
			err = os.MkdirAll(l.LogDir, 0777)
			if nil != err {
				panic(fmt.Errorf("创建日志存放目录失败(LogDir: %s; Error: %s)", l.LogDir, err))
			}
		}
		/**
		 * 维护日志写入句柄
		 */
		var discardWriter Writer
		l.task = crons.New()
		l.task.AddFunc(l.TimeInterval.String(), func() {
			filename := path.Join(l.LogDir, fmt.Sprintf("%s%s.log", l.Prefix, time.Now().Format(l.TimeInterval.DataFormat())))
			if fp, err := files.OpenFile(filename); err == nil {
				l.Writer = log.New(fp, "\r\n", log.LstdFlags)
				if l.fp != nil {
					l.fp.Close()
				}
				l.fp = fp
			} else {
				if discardWriter == nil {
					discardWriter = log.New(io.Discard, "", log.LstdFlags)
				}
				l.Writer = discardWriter
			}
		}, true)
		l.task.Start()
	}
}

/**
 * 停用组件
 */
func (l *Logger) Close() {
	if l.task != nil {
		l.task.Stop()
	}
	if l.fp != nil {
		l.fp.Close()
	}
}

/**
 * 创建 mysql 日志组件
 */
func NewLogger(config ConfLog, writer Writer) logger.Interface {
	log := &Logger{
		Writer:  writer,
		ConfLog: config,
	}
	// 启动日志
	log.Run()
	return log
}

/**
 * 创建一个 Stdout 显示的日志
 */
func StdoutLogger() logger.Interface {
	return NewLogger(ConfLog{
		Config: logger.Config{
			SlowThreshold:             200 * time.Millisecond,
			LogLevel:                  logger.Warn,
			IgnoreRecordNotFoundError: false,
			Colorful:                  true,
		},
	}, log.New(os.Stdout, "\r\n", log.LstdFlags))
}

/**
 * 创建一个丢弃数据的日志
 */
func DiscardLogger() logger.Interface {
	return NewLogger(ConfLog{}, log.New(io.Discard, "", log.LstdFlags))
}
